package cn.iocoder.yudao.module.hs.service.scan;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.hs.controller.admin.material.vo.MaterialRespVO;
import cn.iocoder.yudao.module.hs.controller.admin.materialbox.vo.MaterialBoxRespVO;
import cn.iocoder.yudao.module.hs.controller.admin.picktaskmaterial.vo.StartPickMaterialVO;
import cn.iocoder.yudao.module.hs.controller.admin.picktaskmaterial.vo.StartPickVO;
import cn.iocoder.yudao.module.hs.controller.admin.rfid.vo.RfidSaveReqVO;
import cn.iocoder.yudao.module.hs.controller.admin.scan.vo.ScanInfoRespVO;
import cn.iocoder.yudao.module.hs.controller.admin.scan.vo.ScanManualAdjustReqVO;
import cn.iocoder.yudao.module.hs.controller.admin.scan.vo.ScanMergeReqVO;
import cn.iocoder.yudao.module.hs.controller.admin.scan.vo.ScanPushReqVO;
import cn.iocoder.yudao.module.hs.controller.admin.tray.vo.TraySaveReqVO;
import cn.iocoder.yudao.module.hs.dal.dataobject.material.MaterialDO;
import cn.iocoder.yudao.module.hs.dal.dataobject.materialbox.MaterialBoxDO;
import cn.iocoder.yudao.module.hs.dal.dataobject.materialmode.MaterialModeDO;
import cn.iocoder.yudao.module.hs.dal.dataobject.picktask.PickTaskDO;
import cn.iocoder.yudao.module.hs.dal.dataobject.picktaskmaterial.PickTaskMaterialDO;
import cn.iocoder.yudao.module.hs.dal.dataobject.picktaskmaterialdetail.PickTaskMaterialDetailDO;
import cn.iocoder.yudao.module.hs.dal.dataobject.rfid.RfidDO;
import cn.iocoder.yudao.module.hs.dal.dataobject.tray.TrayDO;
import cn.iocoder.yudao.module.hs.dal.dataobject.warehouse.WarehouseDO;
import cn.iocoder.yudao.module.hs.enums.material.MaterialOperateTypeEnum;
import cn.iocoder.yudao.module.hs.enums.warehouse.WarehouseStatusEnum;
import cn.iocoder.yudao.module.hs.framework.core.annotations.MaterialLog;
import cn.iocoder.yudao.module.hs.framework.core.utils.MaterialLogUtils;
import cn.iocoder.yudao.module.hs.netty.RFIDUtil;
import cn.iocoder.yudao.module.hs.netty.Transmission;
import cn.iocoder.yudao.module.hs.service.material.MaterialService;
import cn.iocoder.yudao.module.hs.service.materialbox.MaterialBoxService;
import cn.iocoder.yudao.module.hs.service.materialmode.MaterialModeService;
import cn.iocoder.yudao.module.hs.service.picktask.PickTaskService;
import cn.iocoder.yudao.module.hs.service.picktaskmaterial.PickTaskMaterialService;
import cn.iocoder.yudao.module.hs.service.picktaskmaterialdetail.PickTaskMaterialDetailService;
import cn.iocoder.yudao.module.hs.service.rfid.RfidService;
import cn.iocoder.yudao.module.hs.service.tray.TrayService;
import cn.iocoder.yudao.module.hs.service.warehouse.WarehouseService;
import cn.iocoder.yudao.module.hs.util.RandomUtil;
import cn.iocoder.yudao.module.hs.util.RedisUtil;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFutureListener;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;

import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;

import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils.getLoginUserId;
import static cn.iocoder.yudao.module.hs.enums.ErrorCodeConstants.*;

/**
 * 扫码相关 Service 接口
 *
 * @author 航盛
 */
@Service
@Validated
@Slf4j
public class ScanServiceImpl implements ScanService {

    private static final int MAX_RETRY = 3;

    @Resource
    private PickTaskService pickTaskService;
    @Resource
    private MaterialService materialService;
    @Resource
    private RfidService rfidService;
    @Resource
    private PickTaskMaterialService pickTaskMaterialService;
    @Resource
    private TrayService trayService;
    @Resource
    private MaterialBoxService materialBoxService;
    @Resource
    private AdminUserApi adminUserApi;
    @Resource
    private WarehouseService warehouseService;
    @Resource
    private PickTaskMaterialDetailService pickTaskMaterialDetailService;
    @Resource
    private MaterialModeService materialModeService;

    @Override
    public List<MaterialDO> pushScanMaterials(ScanPushReqVO scanPushReqVO) {
        if (CollUtil.isEmpty(scanPushReqVO.getPickMaterialCodeList())
                && CollUtil.isEmpty(scanPushReqVO.getPickMaterialBoxCodeList())
                && CollUtil.isEmpty(scanPushReqVO.getPickMaterialRfidCodeList())) {
            throw exception(START_PICK_PUSH_EMPTY);
        }
        PickTaskDO pickTask = pickTaskService.getPickTask(scanPushReqVO.getPickTaskId());
        if (pickTask == null) {
            throw exception(PICK_TASK_NOT_EXISTS);
        }
        PickTaskMaterialDO pickTaskMaterial = pickTaskMaterialService.getPickTaskMaterial(pickTask.getId(), scanPushReqVO.getMaterialModeCode());
        if (pickTaskMaterial == null) {
            throw exception(PICK_TASK_MATERIAL_NOT_EXISTS);
        }
        List<MaterialDO> selectedMaterials = RedisUtil.getSelectedMaterials(pickTask.getCode());
        List<MaterialDO> thisModeSelectedMaterials = new ArrayList<>();
        if (!CollUtil.isEmpty(selectedMaterials)) {
            thisModeSelectedMaterials = selectedMaterials.stream()
                    .filter(material -> material.getModeCode().equals(scanPushReqVO.getMaterialModeCode()))
                    .collect(Collectors.toList());
        }
        TrayDO trayDO;
        if (!CollUtil.isEmpty(thisModeSelectedMaterials)) {
            // 检查是否已存在相同编码的物料
            boolean hasDuplicateMaterialCode = false;
            if (CollUtil.isNotEmpty(scanPushReqVO.getPickMaterialCodeList())) {
                hasDuplicateMaterialCode = thisModeSelectedMaterials.stream()
                        .anyMatch(material -> scanPushReqVO.getPickMaterialCodeList().contains(material.getCode()));
            }
            boolean hasDuplicateMaterialBoxCode = false;
            if (CollUtil.isNotEmpty(scanPushReqVO.getPickMaterialBoxCodeList())) {
                hasDuplicateMaterialBoxCode = thisModeSelectedMaterials.stream()
                        .anyMatch(material -> scanPushReqVO.getPickMaterialBoxCodeList().contains(material.getBoxCode()));
            }
            boolean hasDuplicateMaterialRfidCode = false;
            if (CollUtil.isNotEmpty(scanPushReqVO.getPickMaterialRfidCodeList())) {
                String rfidCode = scanPushReqVO.getPickMaterialRfidCodeList().get(0);
                trayDO = trayService.getTrayByRfidCode(rfidCode);
                if (trayDO != null) {
                    hasDuplicateMaterialRfidCode = thisModeSelectedMaterials.stream()
                            .anyMatch(material -> trayDO.getCode().equals(material.getTrayCode()));
                }
            } else {
                trayDO = null;
            }
            if (hasDuplicateMaterialCode || hasDuplicateMaterialBoxCode || hasDuplicateMaterialRfidCode) {
                throw exception(START_PICK_PUSH_DUPLICATE);
            }
        } else {
            trayDO = null;
        }
        List<MaterialDO> materialListByBoxCode = new ArrayList<>();
        if (CollUtil.isNotEmpty(scanPushReqVO.getPickMaterialBoxCodeList())) {
            materialListByBoxCode = materialService.getMaterialListByBoxCode(scanPushReqVO.getPickMaterialBoxCodeList());
        }
        List<MaterialDO> materialListByCode = new ArrayList<>();
        if (CollUtil.isNotEmpty(scanPushReqVO.getPickMaterialCodeList())) {
            materialListByCode = materialService.getMaterialListByCode(scanPushReqVO.getPickMaterialCodeList());
        }
        List<MaterialDO> materialListByRfidCode = new ArrayList<>();
        if (CollUtil.isNotEmpty(scanPushReqVO.getPickMaterialRfidCodeList())) {
            String rfidCode = scanPushReqVO.getPickMaterialRfidCodeList().get(0);
            TrayDO tray = trayService.getTrayByRfidCode(rfidCode);
            if (tray == null) {
                tray = trayService.getTray(rfidCode);
            }
            materialListByRfidCode = materialService.getMaterialListByTrayCode(tray.getCode());
        }

        int scanMaterialCount = thisModeSelectedMaterials.size();
        if (CollUtil.isEmpty(selectedMaterials)) {
            selectedMaterials = new ArrayList<>();
        }
        // 已拣数量
        int pickedCount = scanMaterialCount + materialListByCode.size() + materialListByBoxCode.size() + materialListByRfidCode.size();
        // 已拣数量 大于 待拣数量
        if (pickedCount > pickTaskMaterial.getMaterialCount()) {
            throw exception(START_PICK_MATERIAL_COUNT_OVER_MAX);
        }
        selectedMaterials.addAll(materialListByCode);
        selectedMaterials.addAll(materialListByBoxCode);
        selectedMaterials.addAll(materialListByRfidCode);
        RedisUtil.setSelectedMaterials(pickTask.getCode(), selectedMaterials);

        List<ScanInfoRespVO> pickedList = RedisUtil.getPickedList(pickTask.getCode());
        if (CollUtil.isEmpty(pickedList)) {
            pickedList = new ArrayList<>();
        }
        if (CollUtil.isNotEmpty(scanPushReqVO.getPickMaterialCodeList())) {
            for (String materialCode : scanPushReqVO.getPickMaterialCodeList()) {
                ScanInfoRespVO scanInfoRespVO = new ScanInfoRespVO();
                scanInfoRespVO.setInfoName("");
                scanInfoRespVO.setInfoCode(materialCode);
                scanInfoRespVO.setMaterialCode(materialCode);
                scanInfoRespVO.setCount(1);
                pickedList.add(scanInfoRespVO);
            }
            RedisUtil.setPickedList(pickTask.getCode(), pickedList);
        }
        if (CollUtil.isNotEmpty(scanPushReqVO.getPickMaterialBoxCodeList())) {
            for (String materialBoxCode : scanPushReqVO.getPickMaterialBoxCodeList()) {
                ScanInfoRespVO scanInfoRespVO = new ScanInfoRespVO();
                scanInfoRespVO.setInfoName("");
                scanInfoRespVO.setInfoCode(materialBoxCode);
                scanInfoRespVO.setMaterialBoxCode(materialBoxCode);
                List<MaterialDO> materialDOList = materialService.getMaterialListByBoxCode(CollectionUtil.toList(materialBoxCode));
                scanInfoRespVO.setCount(materialDOList.size());
                pickedList.add(scanInfoRespVO);
            }
            RedisUtil.setPickedList(pickTask.getCode(), pickedList);
        }
        if (CollUtil.isNotEmpty(scanPushReqVO.getPickMaterialRfidCodeList())) {
            for (String rfidCode : scanPushReqVO.getPickMaterialRfidCodeList()) {
                ScanInfoRespVO scanInfoRespVO = new ScanInfoRespVO();
                scanInfoRespVO.setInfoName("托盘编码");
                scanInfoRespVO.setInfoCode(rfidCode);
                TrayDO tray = trayService.getTrayByRfidCode(rfidCode);
                List<MaterialDO> materialDOList = materialService.getMaterialListByTrayCode(tray.getCode());
                scanInfoRespVO.setCount(materialDOList.size());
                pickedList.add(scanInfoRespVO);
            }
//            RedisUtil.setPickedList(pickTask.getCode(), getLoginUserId(), list);
        }
        return selectedMaterials;
    }

    @Override
    public List<ScanInfoRespVO> getPickedList(Long pickTaskId) {
        PickTaskDO pickTask = pickTaskService.getPickTask(pickTaskId);
        if (pickTask == null) {
            throw exception(PICK_TASK_NOT_EXISTS);
        }
        List<ScanInfoRespVO> pickedList = RedisUtil.getPickedList(pickTask.getCode());
        if (CollUtil.isEmpty(pickedList)) {
            return Collections.emptyList();
        }
        return pickedList;
    }

    @Override
    @MaterialLog(operateType = MaterialOperateTypeEnum.MERGE)
    public void merge(ScanMergeReqVO scanMergeReqVO) {
        PickTaskDO pickTask = pickTaskService.getPickTask(scanMergeReqVO.getPickTaskId());
        if (pickTask == null) {
            throw exception(PICK_TASK_NOT_EXISTS);
        }
        if (CollUtil.isEmpty(scanMergeReqVO.getPickCodeList())) {
            throw exception(START_PICK_PICKED_EMPTY);
        }
        RfidDO rfid = rfidService.getRfid(scanMergeReqVO.getCode());
        TrayDO tray = null;
        if (rfid != null) {
            if (StrUtil.isEmpty(rfid.getTrayCode())) {
                String trayCode;
                do {
                    trayCode = "T" + scanMergeReqVO.getCode() + RandomUtil.generateDigitRandom();
                } while (trayService.getTray(trayCode) != null);
                TraySaveReqVO traySaveReqVO = new TraySaveReqVO();
                traySaveReqVO.setRfidCode(scanMergeReqVO.getCode());
                traySaveReqVO.setCode(trayCode);
                traySaveReqVO.setWarehouseId(pickTask.getWarehouseId());
                traySaveReqVO.setDeptId(pickTask.getDeptId());
                traySaveReqVO.setWarehousingTime(LocalDateTime.now());
                traySaveReqVO.setStatus(WarehouseStatusEnum.ENTRY.getStatus());
                trayService.createTray(traySaveReqVO);
                tray = trayService.getTray(trayCode);
                rfid.setTrayCode(tray.getCode());
                rfid.setWarehousingTime(LocalDateTime.now());
                rfidService.updateRfid(BeanUtils.toBean(rfid, RfidSaveReqVO.class));
            } else {
                tray = trayService.getTrayByRfidCode(rfid.getCode());
            }
        } else {
            tray = trayService.getTray(scanMergeReqVO.getCode());
        }
        if (tray == null) {
            throw exception(START_PICK_MERGE_TRAY_RFID_BIND_ERROR);
        }
        List<MaterialBoxDO> materialBoxDOList = new ArrayList<>();
        List<MaterialDO> materialDOList = new ArrayList<>();
        List<MaterialDO> needRemoveBox = new ArrayList<>();
        List<ScanInfoRespVO> pickedList = RedisUtil.getPickedList(pickTask.getCode());
        for (String code : scanMergeReqVO.getPickCodeList()) {
            MaterialBoxDO materialBox = materialBoxService.getMaterialBox(code);
            if (materialBox != null) {
                materialBoxDOList.add(materialBox);
                pickedList.removeIf(scanInfoRespVO -> scanInfoRespVO.getInfoCode().equals(code));
            } else {
                MaterialDO material = materialService.getMaterial(code);
                if (material != null) {
                    materialDOList.add(material);
                    needRemoveBox.add(material);
                    pickedList.removeIf(scanInfoRespVO -> scanInfoRespVO.getInfoCode().equals(code));
                }
            }
        }
        // 调整完成之后，原托盘如果空了，就解绑
        this.updateTrayUnbind(materialDOList);
        // 将物料箱码去除，并更新新托盘码
        materialService.updateMaterialTray(materialDOList, tray.getCode(), null);
        if (CollUtil.isNotEmpty(materialBoxDOList)) {
            List<String> materialBoxCodeList = CollectionUtils.convertList(materialBoxDOList, MaterialBoxDO::getCode);
            List<MaterialDO> materialListByBoxCode = materialService.getMaterialListByBoxCode(materialBoxCodeList);
            materialDOList.addAll(materialListByBoxCode);
        }
        // 将物料箱更新新托盘码
        materialBoxService.updateMaterialBoxTray(materialBoxDOList, tray.getCode());
        List<MaterialDO> selectedMaterials = RedisUtil.getSelectedMaterials(pickTask.getCode());
        if (CollUtil.isNotEmpty(selectedMaterials)) {
            for (MaterialDO selectedMaterial : selectedMaterials) {
                for (MaterialDO materialDO : materialDOList) {
                    if (selectedMaterial.getCode().equals(materialDO.getCode())) {
                        selectedMaterial.setTrayCode(tray.getCode());
                    }
                }
                for (MaterialDO removeBox : needRemoveBox) {
                    if (selectedMaterial.getCode().equals(removeBox.getCode())) {
                        selectedMaterial.setBoxCode(null);
                    }
                }
            }
            RedisUtil.setSelectedMaterials(pickTask.getCode(), selectedMaterials);
        }
        if (CollUtil.isEmpty(pickedList)) {
            RedisUtil.removePickedList(pickTask.getCode());
            RedisUtil.removeStartPick(pickTask.getCode());
        } else {
            RedisUtil.setPickedList(pickTask.getCode(), pickedList);
        }
    }

    /**
     * 调整完成之后，原托盘如果空了，就解绑
     * @param materialDOList
     */
    private void updateTrayUnbind(List<MaterialDO> materialDOList) {
        Set<String> trayCodes = materialDOList.stream().map(MaterialDO::getTrayCode).collect(Collectors.toSet());
        Set<String> unbindTrayCodes = new HashSet<>();
        for (String trayCode : trayCodes) {
            List<MaterialDO> materialListByTrayCode = materialService.getMaterialListByTrayCode(trayCode);
            if (CollUtil.isEmpty(materialListByTrayCode)) {
                unbindTrayCodes.add(trayCode);
            }
        }
        if (CollUtil.isNotEmpty(unbindTrayCodes)) {
            rfidService.unbind(unbindTrayCodes);
        }
    }

    @Override
    @Transactional
    public void create(String code) {
        TrayDO trayDO = trayService.getTray(code);
        RfidDO rfid = rfidService.getRfid(code);
        if (trayDO != null || rfid != null) {
            throw exception(START_PICK_MERGE_CREATE_EXISTS);
        }
        AdminUserRespDTO user = adminUserApi.getUser(getLoginUserId());
        WarehouseDO warehouse = warehouseService.getWarehouse(user.getWarehouseId());
        String trayCode;
        do {
            trayCode = "T" + code + RandomUtil.generateDigitRandom();
        } while (trayService.getTray(trayCode) != null);
        RfidSaveReqVO rfidSaveReqVO = new RfidSaveReqVO();
        rfidSaveReqVO.setTrayCode(trayCode);
        rfidSaveReqVO.setCode(code);
        rfidSaveReqVO.setLowBattery(false);
        rfidSaveReqVO.setWarehousingTime(LocalDateTime.now());
        rfidSaveReqVO.setDeptId(warehouse.getDeptId());
        rfidService.createRfid(rfidSaveReqVO);
        TraySaveReqVO traySaveReqVO = new TraySaveReqVO();
        traySaveReqVO.setRfidCode(code);
        traySaveReqVO.setCode(trayCode);
        traySaveReqVO.setWarehouseId(user.getWarehouseId());
        traySaveReqVO.setDeptId(warehouse.getDeptId());
        traySaveReqVO.setWarehousingTime(LocalDateTime.now());
        traySaveReqVO.setStatus(WarehouseStatusEnum.ENTRY.getStatus());
        trayService.createTray(traySaveReqVO);
    }

    @Override
    public List<MaterialRespVO> getToBePickedMaterialList(Long pickTaskId, String rfidCode, Integer light) {
        PickTaskDO pickTask = pickTaskService.getPickTask(pickTaskId);
        if (pickTask == null) {
            throw exception(PICK_TASK_NOT_EXISTS);
        }
        StartPickVO startPick = RedisUtil.getStartPick(pickTask.getCode());
        for (StartPickMaterialVO startPickMaterialVO : startPick.getList()) {
            if (startPickMaterialVO.getRfidCode().equals(rfidCode) && startPickMaterialVO.getLight().equals(light)) {
                return startPickMaterialVO.getMaterialRespVOList();
            }
        }
        return Collections.emptyList();
    }

    @Override
    public List<MaterialBoxRespVO> getToBePickedMaterialBoxList(Long pickTaskId, String rfidCode, Integer light) {
        PickTaskDO pickTask = pickTaskService.getPickTask(pickTaskId);
        if (pickTask == null) {
            throw exception(PICK_TASK_NOT_EXISTS);
        }
        List<MaterialRespVO> materialRespVOList = null;
        StartPickVO startPick = RedisUtil.getStartPick(pickTask.getCode());
        for (StartPickMaterialVO startPickMaterialVO : startPick.getList()) {
            if (startPickMaterialVO.getRfidCode().equals(rfidCode) && startPickMaterialVO.getLight().equals(light)) {
                materialRespVOList = startPickMaterialVO.getMaterialRespVOList();
            }
        }
        List<MaterialBoxRespVO> materialBoxRespVOList = new ArrayList<>();
        if (materialRespVOList != null) {
            materialBoxRespVOList = materialRespVOList.stream()
                    .collect(Collectors.groupingBy(
                            MaterialRespVO::getBoxCode,
                            Collectors.summingInt(material -> 1)
                    ))
                    .entrySet()
                    .stream()
                    .map(entry -> {
                        MaterialBoxRespVO vo = new MaterialBoxRespVO();
                        vo.setCode(entry.getKey());
                        vo.setMaterialCount(entry.getValue());
                        return vo;
                    }).collect(Collectors.toList());
        }
        return materialBoxRespVOList;
    }

    @Override
    public void lightFlash(String rfidCode, Integer color) {
        String command = RFIDUtil.getCommand(rfidCode, color, true, 5, true);
        RfidDO rfid = rfidService.getRfid(rfidCode);
        if (rfid == null) {
            throw exception(RFID_NOT_EXISTS);
        }
        TrayDO tray = trayService.getTray(rfid.getTrayCode());
        if (tray == null) {
            throw exception(TRAY_NOT_EXISTS);
        }
        if (ObjUtil.isNull(tray.getWarehouseId())) {
            throw exception(TRAY_RFID_NOT_IN_WAREHOUSE);
        }
        Set<String> deviceIdSet = Transmission.getWarehouseDeviceMap().get(tray.getWarehouseId());
        if (CollUtil.isEmpty(deviceIdSet)) {
            throw exception(DEVICE_WAREHOUSE_NOT_BIND);
        }
        Map<String, Channel> targetChannels = Transmission.getChannelMap()
                .entrySet()
                .stream()
                .filter(entry -> deviceIdSet.contains(entry.getKey()))
                .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
        targetChannels.forEach((deviceId, channel) -> {
            log.info("#####发送执行任务闪灯指令：基站ID - {}，指令信息 - {}", deviceId, command);
            sendMessageWithRetry(channel, command, 0);
        });
    }

    @Override
    public ScanInfoRespVO getTrayRfidInfo(String code) {
        TrayDO trayDO = trayService.getTray(code);
        RfidDO rfid = rfidService.getRfid(code);
        if (trayDO == null && rfid == null) {
            MaterialBoxDO materialBox = materialBoxService.getMaterialBox(code);
            if (materialBox == null) {
                return null;
            }
            List<MaterialDO> materialListByBoxCode = materialService.getMaterialListByBoxCode(CollectionUtil.toList(materialBox.getCode()));
            ScanInfoRespVO scanInfoRespVO = new ScanInfoRespVO();
            scanInfoRespVO.setInfoCode(code);
            scanInfoRespVO.setInfoName("箱");
            if (CollUtil.isNotEmpty(materialListByBoxCode)) {
                scanInfoRespVO.setMaterialModeCode(materialListByBoxCode.get(0).getModeCode());
                MaterialModeDO materialMode = materialModeService.getMaterialMode(materialListByBoxCode.get(0).getModeCode());
                scanInfoRespVO.setPartNumber(ObjectUtil.isNotNull(materialMode) ? materialMode.getPartNumber() : "/");
                scanInfoRespVO.setPartNumberShort(ObjectUtil.isNotNull(materialMode) ? materialMode.getPartNumberShort() : "/");
                scanInfoRespVO.setWarehousingTime(materialListByBoxCode.get(0).getWarehousingTime());
                scanInfoRespVO.setCount(materialListByBoxCode.size());
                scanInfoRespVO.setMaterialList(BeanUtils.toBean(materialListByBoxCode, MaterialRespVO.class));
                scanInfoRespVO.setWarehouseId(materialListByBoxCode.get(0).getWarehouseId());
            }
            return scanInfoRespVO;
        }
        String trayCode = rfid == null ? code : rfid.getTrayCode();
        List<MaterialDO> materialListByTrayCode = materialService.getMaterialListByTrayCode(trayCode);
        ScanInfoRespVO scanInfoRespVO = new ScanInfoRespVO();
        scanInfoRespVO.setInfoCode(code);
        scanInfoRespVO.setInfoName(rfid == null ? "托盘" : "RFID");
        if (CollUtil.isNotEmpty(materialListByTrayCode)) {
            scanInfoRespVO.setMaterialModeCode(materialListByTrayCode.get(0).getModeCode());
            MaterialModeDO materialMode = materialModeService.getMaterialMode(materialListByTrayCode.get(0).getModeCode());
            scanInfoRespVO.setPartNumber(ObjectUtil.isNotNull(materialMode) ? materialMode.getPartNumber() : "/");
            scanInfoRespVO.setPartNumberShort(ObjectUtil.isNotNull(materialMode) ? materialMode.getPartNumberShort() : "/");
            scanInfoRespVO.setWarehousingTime(materialListByTrayCode.get(0).getWarehousingTime());
            scanInfoRespVO.setCount(materialListByTrayCode.size());
            scanInfoRespVO.setMaterialList(BeanUtils.toBean(materialListByTrayCode, MaterialRespVO.class));
            scanInfoRespVO.setWarehouseId(materialListByTrayCode.get(0).getWarehouseId());
        }
        return scanInfoRespVO;
    }

    @Override
    public List<ScanInfoRespVO> getMaterialBoxInfo(Long pickTaskId, Long shipOrderId, String code) {
        TrayDO trayDO = trayService.getTray(code);
        RfidDO rfid = rfidService.getRfid(code);
        if (trayDO == null && rfid == null) {
            MaterialBoxDO materialBox = materialBoxService.getMaterialBox(code);
            if (materialBox == null) {
                throw exception(TRAY_RFID_NOT_EXISTS);
            }
            List<MaterialDO> materialListByBoxCode = materialService.getMaterialListByBoxCode(CollectionUtil.toList(materialBox.getCode()));
            if (CollUtil.isEmpty(materialListByBoxCode)) {
                return Collections.emptyList();
            }
            List<ScanInfoRespVO> scanInfoRespVOList = new ArrayList<>();
            for (MaterialDO materialDO : materialListByBoxCode) {
                ScanInfoRespVO scanInfoRespVO = new ScanInfoRespVO();
                scanInfoRespVO.setInfoName(materialDO.getCode());
                scanInfoRespVO.setInfoCode(materialDO.getBoxCode());
                scanInfoRespVO.setMaterialCode(materialDO.getCode());
                scanInfoRespVO.setMaterialBoxCode(materialDO.getBoxCode());
                scanInfoRespVO.setCount(1);
                scanInfoRespVO.setPickStatus(2);// 在库
                scanInfoRespVOList.add(scanInfoRespVO);
            }
            return scanInfoRespVOList;
        }
        if (ObjectUtil.isNotNull(pickTaskId)) {
            PickTaskDO pickTask = pickTaskService.getPickTask(pickTaskId);
            if (pickTask == null) {
                throw exception(PICK_TASK_NOT_EXISTS);
            }
            StartPickVO startPick = RedisUtil.getStartPick(pickTask.getCode());
            boolean flg = false;
            for (StartPickMaterialVO startPickMaterialVO : startPick.getList()) {
                if (startPickMaterialVO.getRfidCode().equals(code) && startPickMaterialVO.getLight() == 3) {
                    flg = true;
                    break;
                }
                TrayDO tray = trayService.getTrayByRfidCode(startPickMaterialVO.getRfidCode());
                if (tray != null) {
                    if (tray.getCode().equals(code) && startPickMaterialVO.getLight() == 3) {
                        flg = true;
                        break;
                    }
                }
            }
            if (!flg) {
                throw exception(START_PICK_MERGE_MUST_NEW_OR_GREEN);
            }
        }
        String trayCode = rfid == null ? code : rfid.getTrayCode();
        if (ObjectUtil.isNotNull(shipOrderId)) {
            List<PickTaskMaterialDetailDO> pickTaskMaterialDetailList = pickTaskMaterialDetailService.getPickTaskMaterialDetailByShipOrderId(shipOrderId);
            List<String> collect = pickTaskMaterialDetailList.stream().map(PickTaskMaterialDetailDO::getTrayCode).collect(Collectors.toList());
            if (!collect.contains(trayCode)) {
                throw exception(TRAY_RFID_NOT_IN_SHIP_ORDER);
            }
        }
        List<MaterialDO> materialListByTrayCode = materialService.getMaterialListByTrayCode(trayCode);
        if (CollUtil.isEmpty(materialListByTrayCode)) {
            return Collections.emptyList();
        }
        List<ScanInfoRespVO> scanInfoRespVOList = new ArrayList<>();
        Set<String> boxCodeSet = new HashSet<>();
        for (MaterialDO materialDO : materialListByTrayCode) {
            if (StrUtil.isEmpty(materialDO.getBoxCode())) {
                ScanInfoRespVO scanInfoRespVO = new ScanInfoRespVO();
                scanInfoRespVO.setInfoName(materialDO.getCode());
                scanInfoRespVO.setInfoCode(materialDO.getBoxCode());
                scanInfoRespVO.setMaterialCode(materialDO.getCode());
                scanInfoRespVO.setMaterialBoxCode(materialDO.getBoxCode());
                scanInfoRespVO.setCount(1);
                scanInfoRespVO.setPickStatus(2);// 在库
                scanInfoRespVOList.add(scanInfoRespVO);
            } else {
                boxCodeSet.add(materialDO.getBoxCode());
            }
        }
        if (CollUtil.isNotEmpty(boxCodeSet)) {
            List<MaterialDO> materialListByBoxCode = materialService.getMaterialListByBoxCode(new ArrayList<>(boxCodeSet));
            if (CollUtil.isNotEmpty(materialListByBoxCode)) {
                Map<String, List<MaterialDO>> collect = materialListByBoxCode.stream().collect(Collectors.groupingBy(
                        MaterialDO::getBoxCode,
                        HashMap::new,
                        Collectors.toList()
                ));
                for (String boxCode : collect.keySet()) {
                    ScanInfoRespVO scanInfoRespVO = new ScanInfoRespVO();
                    scanInfoRespVO.setInfoName("");
                    scanInfoRespVO.setInfoCode(boxCode);
                    scanInfoRespVO.setMaterialCode("");
                    scanInfoRespVO.setMaterialBoxCode(boxCode);
                    scanInfoRespVO.setCount(collect.get(boxCode).size());
                    scanInfoRespVO.setPickStatus(2);// 在库
                    scanInfoRespVOList.add(scanInfoRespVO);
                }
            }
        }
        return scanInfoRespVOList;
    }

    @Override
    public ScanInfoRespVO getMaterialBoxByCode(String code) {
        MaterialDO material = materialService.getMaterial(code);
        ScanInfoRespVO scanInfoRespVO = new ScanInfoRespVO();
        if (ObjectUtil.isNotNull(material)) {
            scanInfoRespVO.setInfoName(material.getCode());
            scanInfoRespVO.setInfoCode(material.getBoxCode());
            scanInfoRespVO.setMaterialCode(material.getCode());
            scanInfoRespVO.setMaterialBoxCode(material.getBoxCode());
            scanInfoRespVO.setCount(1);
            return scanInfoRespVO;
        }
        // 是否是物料箱编码
        List<MaterialDO> materials = materialService.getMaterialListByBoxCode(Collections.singletonList(code));
        if (CollectionUtil.isNotEmpty(materials)) {
            scanInfoRespVO = new ScanInfoRespVO();
            scanInfoRespVO.setInfoName("");
            scanInfoRespVO.setInfoCode(code);
            scanInfoRespVO.setMaterialCode("");
            scanInfoRespVO.setMaterialBoxCode(code);
            scanInfoRespVO.setCount(materials.size());
            return scanInfoRespVO;
        }
        throw exception(TRAY_RFID_NOT_EXISTS);
    }

    @Override
    public void manualAdjust(ScanManualAdjustReqVO reqVO) {
        TrayDO trayDO = trayService.getTray(reqVO.getCode());
        RfidDO rfid = rfidService.getRfid(reqVO.getCode());
        if (ObjectUtil.isNull(trayDO) && ObjectUtil.isNull(rfid)) {
            MaterialBoxDO materialBox = materialBoxService.getMaterialBox(reqVO.getCode());
            if (materialBox == null) {
                throw exception(TRAY_RFID_NOT_EXISTS);
            }
            if (1 == reqVO.getAdjustType()) {
                List<ScanManualAdjustReqVO.AdjustMaterial> addList = reqVO.getList().stream()
                        .filter(item -> item.getStatus() == 2).collect(Collectors.toList());
                doMergeBox(materialBox.getTrayCode(), materialBox.getCode(), addList);
            }
            // 拆托盘 取出减少的物料信息
            if (2 == reqVO.getAdjustType()) {
                List<ScanManualAdjustReqVO.AdjustMaterial> reduceList = reqVO.getList().stream()
                        .filter(item -> item.getStatus() == 3).collect(Collectors.toList());
                doSplitBox(reduceList);
            }
            return;
        }
        String trayCode = ObjectUtil.isNull(rfid) ? reqVO.getCode() : rfid.getTrayCode();
        // 合并托盘 取出新增的物料信息
        if (1 == reqVO.getAdjustType()) {
            List<ScanManualAdjustReqVO.AdjustMaterial> addList = reqVO.getList().stream()
                    .filter(item -> item.getStatus() == 2).collect(Collectors.toList());
            doMergeTray(trayCode, addList);
        }
        // 拆托盘 取出减少的物料信息
        if (2 == reqVO.getAdjustType()) {
            List<ScanManualAdjustReqVO.AdjustMaterial> reduceList = reqVO.getList().stream()
                    .filter(item -> item.getStatus() == 3).collect(Collectors.toList());
            doSplitTray(reduceList);
        }
    }

    /**
     * 合并托盘
     *
     * @param trayCode 托盘编码
     * @param addList  新增物料信息
     */
    @Override
    @MaterialLog(operateType = MaterialOperateTypeEnum.MERGE_TRAY)
    public void doMergeTray(String trayCode, List<ScanManualAdjustReqVO.AdjustMaterial> addList) {
        List<Long> materialIds = new ArrayList<>();
        for (ScanManualAdjustReqVO.AdjustMaterial adjustMaterial : addList) {
            MaterialDO material = materialService.getMaterial(adjustMaterial.getMaterialCode());
            // 是物料直接更新托盘信息
            if (ObjectUtil.isNotNull(material)) {
                // 更新物料托盘编码
                materialService.updateTrayCodeByCode(trayCode, adjustMaterial.getMaterialCode());
                // 清空物料箱
                materialService.updateNullBoxCodeByCode(adjustMaterial.getMaterialCode());
                materialIds.add(material.getId());
                continue;
            }
            // 是否是物料箱编码
            List<MaterialDO> materials = materialService.getMaterialListByBoxCode(
                    Collections.singletonList(adjustMaterial.getMaterialBoxCode()));
            if (CollectionUtil.isNotEmpty(materials)) {
                // 更新物料箱托盘编码
                materialBoxService.updateTrayCodeByCode(trayCode, adjustMaterial.getMaterialBoxCode());
                // 更新物料托盘编码
                materials.forEach(materialDO -> materialService.updateTrayCodeByCode(
                        trayCode, materialDO.getCode()));
                materialIds.addAll(materials.stream().map(MaterialDO::getId).collect(Collectors.toList()));
                continue;
            }
            throw exception(TRAY_RFID_NOT_EXISTS);
        }
        if (CollUtil.isNotEmpty(materialIds)) {
            MaterialLogUtils.setMaterialInfo(materialIds);
        }
    }

    /**
     * 合并物料箱
     * @param trayCode
     * @param boxCode
     * @param addList
     */
    @Override
    @MaterialLog(operateType = MaterialOperateTypeEnum.MERGE_BOX)
    public void doMergeBox(String trayCode, String boxCode, List<ScanManualAdjustReqVO.AdjustMaterial> addList) {
        List<Long> materialIds = new ArrayList<>();
        for (ScanManualAdjustReqVO.AdjustMaterial adjustMaterial : addList) {
            MaterialDO material = materialService.getMaterial(adjustMaterial.getMaterialCode());
            // 是物料直接更新托盘信息
            if (ObjectUtil.isNotNull(material)) {
                // 更新物料托盘编码
                materialService.updateTrayCodeBoxCodeByCode(trayCode, boxCode, adjustMaterial.getMaterialCode());
                materialIds.add(material.getId());
            }
        }
        if (CollUtil.isNotEmpty(materialIds)) {
            MaterialLogUtils.setMaterialInfo(materialIds);
        }
    }

    /**
     * 拆托盘
     *
     * @param reduceList 减少物料信息
     */
    @Override
    @MaterialLog(operateType = MaterialOperateTypeEnum.SPLIT_TRAY)
    public void doSplitTray(List<ScanManualAdjustReqVO.AdjustMaterial> reduceList) {
        List<Long> materialIds = new ArrayList<>();
        for (ScanManualAdjustReqVO.AdjustMaterial adjustMaterial : reduceList) {
            MaterialDO material = materialService.getMaterial(adjustMaterial.getMaterialCode());
            // 是物料直接更新托盘信息
            if (ObjectUtil.isNotNull(material)) {
                String trayCode = material.getTrayCode();
                // 物料托盘编码
                materialService.updateNullTrayCodeByCode(adjustMaterial.getMaterialCode());
                // 清空物料箱
                materialService.updateNullBoxCodeByCode(adjustMaterial.getMaterialCode());
                // 如果托盘空了，解绑托盘
                List<MaterialDO> materialListByTrayCode = materialService.getMaterialListByTrayCode(trayCode);
                if (CollUtil.isEmpty(materialListByTrayCode)) {
                    rfidService.unbind(Collections.singleton(trayCode));
                }
                materialIds.add(material.getId());
                continue;
            }
            // 是否是物料箱编码
            List<MaterialDO> materials = materialService.getMaterialListByBoxCode(
                    Collections.singletonList(adjustMaterial.getMaterialBoxCode()));
            if (CollectionUtil.isNotEmpty(materials)) {
                String trayCode = materials.get(0).getTrayCode();
                // 更新物料箱托盘编码
                materialBoxService.updateNullTrayCodeByCode(adjustMaterial.getMaterialBoxCode());
                // 更新物料托盘编码
                materials.forEach(materialDO -> materialService.updateNullTrayCodeByCode(materialDO.getCode()));
                // 如果托盘空了，解绑托盘
                List<MaterialDO> materialListByTrayCode = materialService.getMaterialListByTrayCode(trayCode);
                if (CollUtil.isEmpty(materialListByTrayCode)) {
                    rfidService.unbind(Collections.singleton(trayCode));
                }
                materialIds.addAll(materials.stream().map(MaterialDO::getId).collect(Collectors.toList()));
                continue;
            }
            throw exception(TRAY_RFID_NOT_EXISTS);
        }
        if (CollUtil.isNotEmpty(materialIds)) {
            MaterialLogUtils.setMaterialInfo(materialIds);
        }
    }

    /**
     * 拆物料箱
     *
     * @param reduceList 减少物料信息
     */
    @Override
    @MaterialLog(operateType = MaterialOperateTypeEnum.SPLIT_BOX)
    public void doSplitBox(List<ScanManualAdjustReqVO.AdjustMaterial> reduceList) {
        List<Long> materialIds = new ArrayList<>();
        for (ScanManualAdjustReqVO.AdjustMaterial adjustMaterial : reduceList) {
            MaterialDO material = materialService.getMaterial(adjustMaterial.getMaterialCode());
            // 是物料直接更新托盘信息
            if (ObjectUtil.isNotNull(material)) {
                // 清空托盘编码
                materialService.updateNullTrayCodeByCode(adjustMaterial.getMaterialCode());
                // 清空物料箱
                materialService.updateNullBoxCodeByCode(adjustMaterial.getMaterialCode());
                materialIds.add(material.getId());
            }
        }
        if (CollUtil.isNotEmpty(materialIds)) {
            MaterialLogUtils.setMaterialInfo(materialIds);
        }
    }

    public static void sendMessageWithRetry(Channel channel, String command, int retryCount) {
        if (retryCount > MAX_RETRY) {
            // 达到最大重试次数，处理失败逻辑
            return;
        }
        channel.writeAndFlush(command).addListener((ChannelFutureListener) future -> {
            if (!future.isSuccess()) {
                sendMessageWithRetry(channel, command, retryCount + 1);
            }
        });
    }
}
